handlebars-layouts
Handlebars helpers which implement layout blocks similar to Jade, Jinja, Nunjucks, Swig, and Twig.
Install
With Node.js:
$ npm install handlebars-layouts
With Bower:
$ bower install shannonmoeller/handlebars-layouts
API
Helpers are generated by passing in your instance of Handlebars. This allows you to selectively register the helpers on various instances of Handlebars.
layouts(handlebars) : Object
handlebars
Handlebars
- An instance of Handlebars.
Generates an object containing the layout helpers suitible for passing into registerHelper
.
var handlebars = require('handlebars'),
layouts = require('handlebars-layouts');
handlebars.registerHelper(layouts(handlebars));
layouts.register(handlebars) : Object
handlebars
Handlebars
- An instance of Handlebars.
Both generates an object containing the layout helpers and registers them with Handlebars automatically.
var handlebars = require('handlebars'),
layouts = require('handlebars-layouts');
layouts.register(handlebars);
Helpers
{{#extend [partial] [context] [key=value ...]}}
partial
String
- Name of partial to render.context
Object
(Optional) - A custom context for the partial.attributes
Object
(Optional) - Arbitrary values that will be added to the partial data context.
Loads a layout partial of a given name and defines block content.
{{#extend "layout" foo="bar"}}
{{#content "title" mode="prepend"}}Example - {{/content}}
{{/extend}}
The {{#extend}}
helper allows you to reason about your layouts as you would class extension where the above is equivalent to the following psuedo code:
class Page extends Layout {
constructor() {
this.foo = 'bar';
}
title() {
return 'Example - ' + super();
}
}
{{#embed [partial] [context] [key=value ...]}}
partial
String
- Name of partial to render.context
Object
(Optional) - A custom context for the partial.attributes
Object
(Optional) - Arbitrary values that will be added to the partial data context.
Allows you to load a partial which itself extends from a layout. Blocks defined in embedded partials will not conflict with those in the primary layout.
{{#extend "layout"}}
{{#content "body"}}
{{#embed "gallery"}}
{{#content "body"}}
<img src="1.png" alt="" />
<img src="2.png" alt="" />
{{/content}}
{{/embed}}
{{#embed "modal" foo="bar" name=user.fullName}}
{{#content "title" mode="prepend"}}Image 1 - {{/content}}
{{#content "body"}}<img src="1.png" alt="" />{{/content}}
{{/embed}}
{{/content}}
{{/extend}}
The {{#embed}}
helper allows you to reason about your partials as you would class instantiation where the above is equivalent to the following psuedo code:
class Page extends Layout {
body() {
var gallery = new Gallery();
gallery.replaceBody('<img src="1.png" alt="" />\n<img src="2.png" alt="" />');
var modal = new Modal({
foo: 'bar',
name: this.user.fullName
});
modal.prependTitle('Image 1 - ');
modal.replaceBody('<img src="1.png" alt="" />');
return gallery.toString() + modal.toString();
}
}
{{#block [name]}}
name
String
- Block identifier.
Defines a named block, with optional default content. Blocks may have content appended, prepended, or replaced entirely when extending or embedding. You may append and prepend to the same block multiple times.
{{#block "header"}}
<h1>Hello World</h1>
{{/block}}
{{#block "main"}}
<p>Lorem ipsum...</p>
{{/block}}
{{#block "footer"}}
<p>© 1970</p>
{{/block}}
{{#content [name] mode="(append|prepend|replace)"}}
name
String
- Identifier of the block to modify.mode
String
(Optional) - Means of providing block content. Default: replace
.
Sets block content, optionally appending or prepending using the mode
attribute.
Layout:
<html>
...
<body>
{{#block "header"}}
<h1>Hello World</h1>
{{/block}}
{{#block "main"}}
<p>Lorem ipsum.</p>
{{/block}}
{{#block "footer"}}
<p>© 1999</p>
{{/block}}
</body>
</html>
Page:
{{#extend "layout"}}
{{#content "header"}}
<h1>Goodnight Moon</h1>
{{/content}}
{{#content "main" mode="append"}}
<p>Dolor sit amet.</p>
{{/content}}
{{#content "footer" mode="prepend"}}
<p>MIT License</p>
{{/content}}
{{/extend}}
Output:
<html>
...
<body>
<h1>Goodnight Moon</h1>
<p>Lorem ipsum.</p>
<p>Dolor sit amet.</p>
<p>MIT License</p>
<p>© 1999</p>
</body>
</html>
Conditional Blocks
There are times where you need to wrap a block with an element or use a different class depending on whether content has been provided for a block. For this purpose, the content
helper may be called as a subexpression to check whether content has been provided for a block.
For example, you may wish to have an optional column in a grid layout:
{{!-- layout.hbs --}}
<div class="grid">
<div class="grid-col {{#if (content "right")}}grid-col_2of3{{else}}grid-col_full{{/if}}">
{{{block "left"}}}
</div>
{{#if (content "right")}}
<div class="grid-col grid-col_1of3">
{{{block "right"}}}
</div>
{{/if}}
</div>
For a page that only needs a left column, you may omit defining content for the right
block:
{{!-- page.html --}}
{{#extend "layout"}}
{{#content "left"}}
<p>Left</p>
{{/content}}
{{/extend}}
Resulting in:
<div class="grid">
<div class="grid-col grid-col_full">
<p>Left</p>
</div>
</div>
For a page with two columns, simply define content for both blocks:
{{!-- page.html --}}
{{#extend "layout"}}
{{#content "left"}}
<p>Left</p>
{{/content}}
{{#content "right"}}
<p>Right</p>
{{/content}}
{{/extend}}
Resulting in:
<div class="grid">
<div class="grid-col grid-col_2of3">
<p>Left</p>
</div>
<div class="grid-col grid-col_1of3">
<p>Right</p>
</div>
</div>
Example
layout.hbs
<!doctype html>
<html lang="en-us">
<head>
{{#block "head"}}
<title>{{title}}</title>
<link rel="stylesheet" href="assets/css/screen.css" />
{{/block}}
</head>
<body>
<div class="site">
<div class="site-hd" role="banner">
{{#block "header"}}
<h1>{{title}}</h1>
{{/block}}
</div>
<div class="site-bd" role="main">
{{#block "body"}}
<h2>Hello World</h2>
{{/block}}
</div>
<div class="site-ft" role="contentinfo">
{{#block "footer"}}
<small>© 2013</small>
{{/block}}
</div>
</div>
{{#block "foot"}}
<script src="assets/js/controllers/home.js"></script>
{{/block}}
</body>
</html>
page.html
{{#extend "layout"}}
{{#content "head" mode="append"}}
<link rel="stylesheet" href="assets/css/home.css" />
{{/content}}
{{#content "body"}}
<h2>Welcome Home</h2>
<ul>
{{#items}}
<li>{{.}}</li>
{{/items}}
</ul>
{{/content}}
{{#content "foot" mode="prepend"}}
<script src="assets/js/analytics.js"></script>
{{/content}}
{{/extend}}
Putting Them Together
var handlebars = require('handlebars');
var layouts = require('handlebars-layouts');
handlebars.registerHelper(layouts(handlebars));
handlebars.registerPartial('layout', fs.readFileSync('layout.hbs', 'utf8'));
var template = handlebars.compile(fs.readFileSync('page.html', 'utf8'));
var output = template({
title: 'Layout Test',
items: [
'apple',
'orange',
'banana'
]
});
console.log(output);
Output (prettified for readability)
<!doctype html>
<html lang="en-us">
<head>
<title>Layout Test</title>
<link rel="stylesheet" href="assets/css/screen.css" />
<link rel="stylesheet" href="assets/css/home.css" />
</head>
<body>
<div class="site">
<div class="site-hd" role="banner">
<h1>Layout Test</h1>
</div>
<div class="site-bd" role="main">
<h2>Welcome Home</h2>
<ul>
<li>apple</li>
<li>orange</li>
<li>banana</li>
</ul>
</div>
<div class="site-ft" role="contentinfo">
<small>© 2013</small>
</div>
</div>
<script src="assets/js/analytics.js"></script>
<script src="assets/js/controllers/home.js"></script>
</body>
</html>
Contribute
Standards for this project, including tests, code coverage, and semantics are enforced with a build tool. Pull requests must include passing tests with 100% code coverage and no linting errors.
Test
$ npm test
© 2015 Shannon Moeller me@shannonmoeller.com
Licensed under MIT